묻고 답해요
129만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨[리액트 2부] 고급 주제와 훅
[4.4장 메모이제이션 훅] 4.4.2 useMemo 에서 every 함수에 관한 질문입니다.
안녕하세요 선생님 every 배열을 비교하는 방법이 궁금해서 로그를 찍어보았습니다.// export default App; import MyReact from "./lib/MyReact"; import React from "react"; const Board = ({ posts, tag }) => { MyReact.resetCursor(); const [darkTheme, setDarkTheme] = React.useState(false); const filterPosts = () => { console.log("filterPosts"); return posts.filter((post) => (tag ? post.tag === tag : true)); }; const filteredPosts = MyReact.useMemo(filterPosts, [posts, tag]); console.log("Board rendered"); return ( <> <div> <button onClick={() => setDarkTheme(!darkTheme)}>Theme Change</button> <span>{darkTheme ? "dark" : "light"}</span> </div> <FilteredPosts value={filteredPosts}></FilteredPosts> </> ); }; const FilteredPosts = MyReact.memo(({ value }) => { console.log("FilteredPosts rendered") return ( <ul> {value.map(({ id, content, tag }) => { return ( <li key={id}> {content} <span>#{tag}</span> </li> ); })} </ul> ); }); export default () => { const [tag, setTag] = React.useState(""); return ( <> <button onClick={() => setTag("")}>ALL</button> <button onClick={() => setTag("tag1")}>Tag1</button> <button onClick={() => setTag("tag2")}>Tag2</button> <Board posts={[ { id: "id1", content: "content1", tag: "tag1" }, { id: "id2", content: "content2", tag: "tag1" }, { id: "id3", content: "content3", tag: "tag2" }, ]} tag={tag} /> </> ); }; function useMemo(nextCreate, deps) { console.log("deps = ", deps); if (!memorizedStates[cursor]) { const nextValue = nextCreate(); memorizedStates[cursor] = [nextValue, deps]; cursor = cursor + 1; return nextValue; } const nextDeps = deps; const [prevValue, prevDeps] = memorizedStates[cursor]; console.log("prevDeps => ", prevDeps); console.log("nextDeps => ", nextDeps); if ( prevDeps.every((prev, index) => { console.log( "comparing -> ", prev, nextDeps[index], prev === nextDeps[index], index ); return prev === nextDeps[index]; }) ) { console.log("it is same"); cursor = cursor + 1; return prevValue; } console.log("it is not same"); const nextValue = nextCreate(); memorizedStates[cursor] = [nextValue, deps]; cursor = cursor + 1; return nextValue; }여기서 궁금한 점은1번째 빨간 paragraph는 theme change를 눌러서 변경이 없는 경우입니다. comparing => 로그를 확인하면every가 2개를 비교하는데 첫번째는 array(posts)를 비교하고두번째는 tag를 비교하는것을 확인 할 수 있습니다. 2번째 빨간 paragraph는 Tag1 버튼을 누른 경우입니다.이때 저는 첫번째 비교는 true이고 두번째 비교에서 빈문자열 vs tag1이니 여기서 false가 나길 기대했지만 결과는 그렇지 않았고 첫번째 array(posts) 비교에서 false가 나왔습니다. 3번째 빨간 paragraph는 다시 theme change를 눌렀고 이는 정상적으로 array(posts) , tag1 비교를 수행한것을 확인할 수 있습니다. 왜 이런것인가요?
-
미해결[리액트 2부] 고급 주제와 훅
[4.3장 리듀서 훅] 4.3.7 활용 MyForm(풀이) 오타 제보 및 질문이 있습니다.
안녕하세요 선생님 코드에 오타가 있습니다.const formReducer = (state, action) => { if (action.type === "SET_VALUES") { console.log("SET_VALUES, state ", state); return { ...state, values: { ...state.values, //value --> values [action.name]: action.value, }, }; }value에서 values로 수정되어야 할 것 같습니다. 질문:1) value 인 상태에서는 전화번호에 숫자를 입력했을 때 onChange event에 의해서 state가 변경이 됨2) 이때 state.value는 undefined 이라 기존에 주소 값에 적어뒀던 "123" 이 무시되고 전화번호 값만 action에 의해서 state에 저장.3) useEffect가 state 변경을 감지하고 validate를 진행후에 reducer의 forceupdate 에 의해 re render 4)주소값이 없었으니 errorr값에 "주소를 입력하세요" 들어있다.라고 이해하면 맞을까요?
-
해결됨[리액트 2부] 고급 주제와 훅
[3.5장 컨택스트 훅] 3.5.2 useContenxt 에서 질문이 있습니다.
안녕하세요 선생님 Count, PlusButton이 re render 되는 조건을 알고 싶습니다. 예전예시에서는 class Consumer extends React.Component { constructor(props) { super(props); this.state = { value: emitter.get(), }; this.setValue = this.setValue.bind(this); } setValue(nextValue) { this.setState({ value: nextValue }); } componentDidMount() { emitter.on(this.setValue); } componentWillUnmount() { emitter.off(this.setValue); } render() { return <>{this.props.children(this.state.value)}</>; } } Consumer가 state를 가지고 있음으로순서가Provider render -> Consumer render -> Consumer componentDidMount -> Provider componentDidMount (set 을 통해 빈 객체였던 것을 value, setValue로 바꿔줌)이때 Consumer state는 emitter.get()임으로 변경된 것을 감지 하고 re render 하는 것으로 이해했었습니다.헌데 이번예시에서는 function useContext(context) { console.log("userContext, context.emitter.get() = ", context.emitter.get()); const [value, setValue] = React.useState(context.emitter.get()); React.useEffect(() => { console.log("Consumer useEffect"); context.emitter.on(setValue); return () => { console.log("Consumer useEffect clean"); context.emitter.off(setValue); }; }, [context]); return value; } const Count = () => { console.log("Count render"); const { count } = MyReact.useContext(countContext); return <div>{count}</div>; };Provider render -> Count(Consumer) render -> Count's useEffect -> Provider's useEffect 을 통해emitter 값이 빈객체에서 count, setCount로 채워지는것은 이해하였습니다.이때 Count 가 다시 한번 re render되는데 왜 그런 것인가요?첫번째 예시처럼 state?같은게 존재하는건가요?다시 render되는 조건이 궁금합니다.
-
해결됨[리액트 2부] 고급 주제와 훅
[1.2장 상품목록 화면] 1.2.3 Button 에서 기본 props? 관련 질문이 있습니다.
안녕하세요 선생님 질문이 있습니다.<Button whatelse={"will"}>주문하기 , 결제하기</Button> --- const Button = ({ whatelse }) => ( <button className="Button brand">{whatelse}</button> ); export default Button;children처럼 제가 따로 설정해주지도 않았는데 기본적으로 생성된 props들은 뭐라고 부르나요? const Button = ({ styleType, block, ...rest }) => { let className = "Button"; if (styleType) className += ` ${styleType}`; if (block) className += ` block`; return <button className={className} {...rest}></button>; }; export default Button; 또한 강의 후반에 ...rest로 children, onClick props 를 퉁치는데 이떄 children props 내용이 return하는 button element에 {children} 이렇게 들어있지 않아도 잘 렌더링이 되던데 이것은 뭐라 부르나요?
-
미해결[리액트 2부] 고급 주제와 훅
비동기 호출을 해줘야하니깐. this binding을 해줘야한다." 라는 말의 의미가 궁금합니다.
안녕하세요.^^ 정환님 강의 잘 보고 있습니다."비동기 호출을 해줘야하니깐. this binding을 해줘야한다."강의를 듣다가 궁금한게 생겼는데 위처럼 말씀을 하셨는데 그 이유가 무엇일까요? (스스로 답을 찾음)내부에 this를 사용하기전에 binding을 습관적처럼 해야겠군요. 대부분 내부에 this.state 등등 리액트 api를 쓸거라면.https://www.freecodecamp.org/news/this-is-why-we-need-to-bind-event-handlers-in-class-components-in-react-f7ea1a6f93eb/
-
미해결따라하며 배우는 리액트 테스트 [2023.11 업데이트]
안녕하세요! 로딩 상태 테스트에 관련된 질문입니다.
제 나름대로 강의를 응용해서 테스트를 짜보려고 강의 시점과 다른 userEvent 버전을 쓰고 있기는 하는데요. 로딩 상태를 테스트할때 마치 로딩 상태를 건너뛰고 바로 리스폰스를 받은 듯이 작동하고 있습니다.... 혹시 userEvent 버전이 달라서 동작이 달라진걸까요? 강의와 코드 내용이 조금 달라진 것에 대한 질문이라 죄송합니다만 아무리 찾아도 이유를 모르곘네요..ㅠㅠㅠimport { screen, render } from '@testing-library/react'; import App from './App'; import userEvent from '@testing-library/user-event'; describe('MainPage', () => { it('should change page when click buttons', async () => { const user = userEvent.setup(); render(<App />); const americaInput = await screen.findByRole('spinbutton', { name: /america/i, }); await user.clear(americaInput); await user.type(americaInput, '2'); const englandInput = await screen.findByRole('spinbutton', { name: /england/i, }); await user.clear(englandInput); await user.type(englandInput, '1'); const dinnerCheckbox = await screen.findByText(/dinner/i); await user.click(dinnerCheckbox); const orderButton = screen.getByRole('button', { name: '주문하기' }); await user.click(orderButton); const summeryHeading = screen.getByRole('heading', { name: '주문 확인' }); expect(summeryHeading).toBeInTheDocument(); const productHeading = screen.getByRole('heading', { name: '여행상품: 3000', }); expect(productHeading).toBeInTheDocument(); const americaLi = screen.getByText('2 America'); const englandLi = screen.getByText('1 England'); expect(americaLi).toBeInTheDocument(); expect(englandLi).toBeInTheDocument(); const optionHeading = screen.getByRole('heading', { name: '옵션: 500' }); const dinnerLi = screen.getByText('Dinner'); expect(optionHeading).toBeInTheDocument(); expect(dinnerLi).toBeInTheDocument(); const confirmCheckbox = screen.getByRole('checkbox'); const submitButton = screen.getByRole('button'); await user.click(confirmCheckbox); await user.click(submitButton); const loading = screen.getByText(/loading/i); expect(loading).toBeInTheDocument(); const completeDiv = await screen.findByRole('heading', { name: '주문이 성공했습니다!', }); const subHeadingDiv = await screen.findByRole('heading', { name: '지금까지 모든 주문', }); const loadingAfterResponse = screen.queryByText(/loading/i); expect(completeDiv).toBeInTheDocument(); expect(subHeadingDiv).toBeInTheDocument(); expect(loadingAfterResponse).not.toBeInTheDocument(); const priceDiv = await screen.findByText('3500', { exact: false }); expect(priceDiv).toBeInTheDocument(); const backBtn = await screen.findByRole('button'); await user.click(backBtn); const newAmericaInput = await screen.findByRole('spinbutton', { name: /america/i, }); expect(newAmericaInput).toBeInTheDocument(); expect(newAmericaInput).toHaveValue(0); }); });
-
미해결따라하며 배우는 리액트 테스트 [2023.11 업데이트]
이미지가 안보인다 하셔서 다시 질문드립니다.
안녕하세요 선생님!말씀 주신대로 7-6강 보고 왔지만... 혼자서는 문제의 원인 파악이 힘들어서 다시 재질문 드립니다 ㅠㅠ로그를 확인해 보니 calculate.test.js에서 Fail이 발생되고 있지만, 원인 파악은 아직 못한 상태입니다 ㅠㅠ 레포지토리 주소도 같이 올려드립니다! PASS src/pages/OrderPage/tests/Type.test.js (7.469 s) FAIL src/pages/OrderPage/tests/Calculate.test.js (7.513 s) ● Console console.error Warning: An update to Type inside a test was not wrapped in act(...). When testing, code that causes React state updates should be wrapped into act(...): act(() => { /* fire events that update state */ }); /* assert on the output */ This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act at fn (D:\www\react-shop-test\src\pages\OrderPage\Type.js:8:17) 17 | try { 18 | const response = await axios.get(`http://localhost:5000/${orderType}`); > 19 | setItems(response.data); | ^ 20 | } catch (err) { 21 | setError(true); 22 | } at printWarning (node_modules/react-dom/cjs/react-dom.development.js:86:30) at error (node_modules/react-dom/cjs/react-dom.development.js:60:7) at warnIfUpdatesNotWrappedWithActDEV (node_modules/react-dom/cjs/react-dom.development.js:27589:9) at scheduleUpdateOnFiber (node_modules/react-dom/cjs/react-dom.development.js:25508:5) at setItems (node_modules/react-dom/cjs/react-dom.development.js:17527:7) at loadItems (src/pages/OrderPage/Type.js:19:7) ● update product's total when products change expect(element).toHaveTextContent() Expected element to have text content: 0 Received: 총 가격: 6 | 7 | const productsTotal = screen.getByText("총 가격:", { exact: false }); > 8 | expect(productsTotal).toHaveTextContent("0"); | ^ 9 | 10 | // const americaInput = await screen.findByRole("spinbutton", { 11 | // name: "America", at Object.<anonymous> (src/pages/OrderPage/tests/Calculate.test.js:8:25) PASS src/App.test.js (7.781 s) ● Console console.error Warning: An update to Type inside a test was not wrapped in act(...). When testing, code that causes React state updates should be wrapped into act(...): act(() => { /* fire events that update state */ }); /* assert on the output */ This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act at fn (D:\www\react-shop-test\src\pages\OrderPage\Type.js:8:17) at div at div at OrderPage at div at App 17 | try { 18 | const response = await axios.get(`http://localhost:5000/${orderType}`); > 19 | setItems(response.data); | ^ 20 | } catch (err) { 21 | setError(true); 22 | } at printWarning (node_modules/react-dom/cjs/react-dom.development.js:86:30) at error (node_modules/react-dom/cjs/react-dom.development.js:60:7) at warnIfUpdatesNotWrappedWithActDEV (node_modules/react-dom/cjs/react-dom.development.js:27589:9) at scheduleUpdateOnFiber (node_modules/react-dom/cjs/react-dom.development.js:25508:5) at setItems (node_modules/react-dom/cjs/react-dom.development.js:17527:7) at loadItems (src/pages/OrderPage/Type.js:19:7) console.error Warning: An update to Type inside a test was not wrapped in act(...). When testing, code that causes React state updates should be wrapped into act(...): act(() => { /* fire events that update state */ }); /* assert on the output */ This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act at fn (D:\www\react-shop-test\src\pages\OrderPage\Type.js:8:17) at div at div at div at OrderPage at div at App 17 | try { 18 | const response = await axios.get(`http://localhost:5000/${orderType}`); > 19 | setItems(response.data); | ^ 20 | } catch (err) { 21 | setError(true); 22 | } at printWarning (node_modules/react-dom/cjs/react-dom.development.js:86:30) at error (node_modules/react-dom/cjs/react-dom.development.js:60:7) at warnIfUpdatesNotWrappedWithActDEV (node_modules/react-dom/cjs/react-dom.development.js:27589:9) at scheduleUpdateOnFiber (node_modules/react-dom/cjs/react-dom.development.js:25508:5) at setItems (node_modules/react-dom/cjs/react-dom.development.js:17527:7) at loadItems (src/pages/OrderPage/Type.js:19:7) Test Suites: 1 failed, 2 passed, 3 total Tests: 1 failed, 4 passed, 5 total Snapshots: 0 total Time: 13.137 s Ran all test suites related to changed files.👇 기존 질문https://www.inflearn.com/questions/985742
-
해결됨부트캠프에서 알려주지 않는 것들 (리액트) 1편
test 코드 작성시 질문
vite를 사용하고 있으며 현재 촐더 구조입니다.현재 jest.setup.ts 파일내용입니다. (챗 gpt에게 물어본 결과 setupTest.ts로 바꿔야한다는 답변을 받았지만 꼭 바꿔야하는지 잘모르겠습니다.)import "@testing-library/jest-dom" import { server } from "./src/mocks/server" beforeAll(() => server.listen()) afterEach(() => server.resetHandlers()) afterAll(() => server.close()) msw를 사용 하기위해 필요한 설정을 해놓았습니다.다음은 테스팅을 위한 컴포넌트 입니다.import axios from "axios" import React, { useEffect, useState } from "react" const Direction: React.FC = () => { const [data, setData] = useState<any>({}) const [isLoading, setIsLoading] = useState(true) useEffect(() => { void (async () => setData(await (await axios("https://jsonplaceholder.typicode.com/todos/1")).data))() setIsLoading(false) }, []) return ( <> {isLoading ? ( <div>loading...</div> ) : ( <ul> <li>{data.id} / </li> <li>{data.title} /</li> <li>{data.userId} /</li> </ul> )} </> ) } export default Direction 보시는 것처럼 데이터를 불러와서 화면에 표시하는 컴포넌트 입니다. 다음은 테스트 케이스 입니다.import { render, screen } from "@testing-library/react" import Direction from "." test("Direction 컴포넌트가 정상적으로 렌더링되는지 테스트", async () => { render(<Direction />) const result = await screen.findAllByRole("listitem") expect(result).toHaveLength(3) })해당 코드를보시면 간단해서 에러없이 깔끔하게 성공합니다.하지만 프로젝트에서 swr을 사용하기로 했는데 여기서 문제가 발생합니다.import React from "react" import useSWR from "swr" const Direction: React.FC = () => { const { isLoading, data } = useSWR("https://jsonplaceholder.typicode.com/todos/1") return ( <> {isLoading ? ( <div>loading...</div> ) : ( <ul> <li>{data.id} / </li> <li>{data.title} /</li> <li>{data.userId} /</li> </ul> )} </> ) } export default Direction 컴포넌트를 swr을 사용하는 방식으로 바꾸고 테스트를 실행해보면 findAllByRole에서 listitem을 찾을수 없다는 에러가 발생합니다.swr의 특성인거같긴한데 도무지 해결방안이 떠오르지 않습니다.react-query나 swr같은 캐싱라이브러리를 사용하면서 msw를 사용하여 테스트를 하고 싶은데 따로 설정해야하는 것들이나 권장되는 코드 작성 방식이 따로 있나요?그것이아니라면 어떤부분을 수정해야 제대로 작동할까요?
-
미해결따라하며 배우는 리액트 테스트 [2023.11 업데이트]
msw 에러
강의를 보다 msw부분에서 에러가 발생합니다. 폴더구조는 이런식이고 handler와 server의 ts파일입니다.setupTests.ts파일입니다 App컴포넌트 입니다 문제의 테스트 코드입니다.강의에서 사용하신 대로 비슷하게 작성하고 에러가 발생할 부분도 제눈에는 보이진 않는데위와같은 에러가 발생하여 옵셔널 체이닝 을 사용하면이런 에러가 발생하네요.npm test가아닌 npm start로 실행시켜 봤을때에는 전혀 이상이 없는데 테스트를 실행시키면 에러가 발생합니다.서버설정부분이나 핸들러부분이 잘못돼었나 싶어서 찾아보았지만 그부분은 이상이 없는것같고app컴포넌트를 다른 방식으로 작성해도 계속에러가 발생합니다.어떤부분이 잘못된부분이고 어떤 해결방안이있을까요?
-
미해결따라하며 배우는 리액트 테스트 [2023.11 업데이트]
toHaveTextContent 에러
안녕하세요.'context wrapper 추가로 에러 제거하기' 강의에서 발생한 에러가 해결되지 않아 질문 드립니다.https://www.inflearn.com/questions/736423/tohavetextcontent-%EC%97%90%EC%84%9C-%EC%97%90%EB%9F%AC%EA%B0%80-%EC%9E%90%EA%BE%B8-%EB%82%98%EB%8A%94%EB%8D%B0-%EC%95%84%EB%AC%B4%EB%A6%AC-%EC%B0%BE%EC%95%84%EB%8F%84-%EC%9E%98-%EB%AA%A8%EB%A5%B4%EA%B2%A0%EC%8A%B5%EB%8B%88%EB%8B%A4이 글과 동일한 에러가 계속 발생하는데, 이 질문자분이 답글로 남겨주신 코드로 수정해보아도 해결이 되지 않습니다. 어디서 문제가 발생한 건지 강의를 다시 보고 또 봐도 찾기가 어려워 깃허브 주소 남깁니다. 감사합니다.● update product's total when products change expect(element).toHaveTextContent() Expected element to have text content: 1000 Received: 총 가격: 0 16 | userEvent.clear(americaInput); 17 | userEvent.type(americaInput, "1"); > 18 | expect(productsTotal).toHaveTextContent("1000"); | ^ 19 | }); 20 | at Object.<anonymous> (src/pages/OrderPage/test/calculate.test.js:18:25) https://github.com/daeunleeeee/react-shop-test
-
해결됨따라하며 배우는 리액트 테스트 [2023.11 업데이트]
This could be because the text is broken up by multiple elements. 에러
선생님 안녕하세요아무리 봐도 오류 이유를 모르겠어서 질문드립니다.제 오류는 아래와 같습니다. (코드상 에러난 부분 ✅ 했습니다)> TestingLibraryElementError: Unable to find an element with the text: /loading/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. // CompletePage.js import axios from "axios"; import React, { useEffect, useContext, useState } from "react"; import ErrorBanner from "../../components/ErrorBanner"; import { OrderContext } from "../../contexts/OrderContext"; function CompletePage({ setStep }) { const [OrderDatas, , resetOrderDatas] = useContext(OrderContext); const [orderHistory, setOrderHistory] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); useEffect(() => { orderCompleted(OrderDatas); }, [OrderDatas]); const orderCompleted = async (OrderDatas) => { try { let response = await axios.post( "http://localhost:5001/order", OrderDatas ); setOrderHistory(response.data); setLoading(false); } catch (error) { setError(true); } }; if (error) { return <ErrorBanner message="에러가 발생했습니다." />; } const orderTable = orderHistory.map((item) => ( <tr key={item.orderNumber}> <td>{item.orderNumber}</td> <td>{item.price}</td> </tr> )); const handleClick = () => { resetOrderDatas(); setStep(0); }; if (loading) { return <div>loading</div>; ✅ } else { return ( <div style={{ textAlign: "center" }}> <h2>주문이 성공했습니다.</h2> <h3>지금까지 모든 주문</h3> <table style={{ margin: "auto" }}> <thead> <tr> <th>주문 번호</th> <th>주문 가격</th> </tr> </thead> <tbody>{orderTable}</tbody> </table> <button onClick={handleClick}>첫페이지로</button> </div> ); } } export default CompletePage; //App.test.js import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import App from "./App"; test("From order to order completion", async () => { render(<App />); const event = userEvent.setup(); const americaInput = await screen.findByRole("spinbutton", { name: "America", }); await event.clear(americaInput); await event.type(americaInput, "2"); // England 여행 상품 3개 추가합니다. const englandInput = await screen.findByRole("spinbutton", { name: "England", }); await event.clear(englandInput); await event.type(englandInput, "3"); // insurance 옵션체크 const insuranceCheckbox = await screen.findByRole("checkbox", { name: "Insurance", }); await event.click(insuranceCheckbox); //모든 주문을 한 이후 주문 버튼 클릭! const orderButton = screen.getByRole("button", { name: "주문", }); await event.click(orderButton); ////////// 주문확인페이지 /////// const summaryHeading = screen.getByRole("heading", { name: "주문 확인" }); expect(summaryHeading).toBeInTheDocument(); const productHeading = screen.getByRole("heading", { name: "여행 상품: 5000", }); expect(productHeading).toBeInTheDocument(); const optionsHeading = screen.getByRole("heading", { name: "옵션: 500", }); expect(optionsHeading).toBeInTheDocument(); expect(screen.getByText("2 America")).toBeInTheDocument(); expect(screen.getByText("3 England")).toBeInTheDocument(); expect(screen.getByText("Insurance")).toBeInTheDocument(); const confirmCheckbox = screen.getByRole("checkbox", { name: "주문하려는것을 확인 하셨나요?", }); await event.click(confirmCheckbox); const confirmOrderButton = screen.getByRole("button", { name: "주문 확인", }); await event.click(confirmOrderButton); //// 주문 완료 //// const loading = screen.getByText(/loading/i); ✅ expect(loading).toBeInTheDocument(); const completeHeader = await screen.findByRole("heading", { name: "주문이 성공했습니다.", }); expect(completeHeader).toBeInTheDocument(); const loadingDisappeared = screen.queryByText("loading"); expect(loadingDisappeared).not.toBeInTheDocument(); const firstPageButton = screen.getByRole("button", { name: "첫페이지로", }); event.click(firstPageButton); const productsTotal = screen.getByText("상품 총 가격: 0"); expect(productsTotal).toBeInTheDocument(); const optionsTotal = screen.getByText("옵션 총 가격: 0"); expect(optionsTotal).toBeInTheDocument(); await waitFor(() => { screen.getByRole("spinbutton", { name: "America" }); }); }); 제가 에러를 이해한 바로는 return 문이 여러 경우로 나뉘어서 그렇다고 생각했습니다. 제대로 이해한게 맞을까요..?( This could be because the text is broken up by multiple elements.)실제로 아래 else return문에 loading을 넣어주니 해결이 되긴 했습니다. 하지만 아래 ✅와 같이 작성해주면 에러가 뜨네요..왜 이러는건지 혹시 아실까요? ㅠㅠif (loading) { return <div>loading</div>; ✅ } else { return ( <div style={{ textAlign: "center" }}> <h2>주문이 성공했습니다.</h2> <h3>지금까지 모든 주문</h3> <table style={{ margin: "auto" }}> <thead> <tr> <th>주문 번호</th> <th>주문 가격</th> </tr> </thead> <tbody>{orderTable}</tbody> </table> <button onClick={handleClick}>첫페이지로</button> </div> ); }선생님 코드를 보고 복붙했는데도 해결이 안되네요.ㅜ
-
해결됨따라하며 배우는 리액트 테스트 [2023.11 업데이트]
Type.test.js파일에 궁금한점이 생겼습니다
강사님 안녕하세요 ~~ 늘 잘 듣고 있습니다.복습을 하다 궁금한점이 생겼는데요!Type.test.js에서 Products컴포넌트의 img태그를 변수 productImages로 집어주신걸 보았습니다. 근데 img태그는 Type.js가 아닌 Products.js에 있는건데,그렇담 Products.test.js 파일을 따로만들어 테스트를 해야하는것이 아닌가 하는 궁금증이 생겼습니다.그냥 Type페이지에 Products 컴포넌트가 들어있기 때문에 Type.test.js에 사용한건가요?test("displays product images from server", async () => { render(<Type orderType="products" />); const productImages = await screen.findAllByRole("img", { name: /product$/i, }); expect(productImages).toHaveLength(2); const altText = productImages.map((element) => element.alt); expect(altText).toEqual(["America product", "England product"]); });
-
해결됨따라하며 배우는 리액트 테스트 [2023.11 업데이트]
toHaveTextContent 에서 에러가 자꾸 나는데 아무리 찾아도 잘 모르겠습니다.
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 선생님 안녕하세요! 강의 너무 잘 듣고 있습니다! 감사해요오류를 잡으려고 노력해봤는데도 잘 안돼서 질문 남깁니다calculate.test.js파일의 toHaveTextContent()부분에서 모두 오류가 나고 있습니다. 선생님이 주신 소스코드와 제 코드를 모두 비교해봤는데 다 똑같더라구요. 제가 인지하지 못한 오류가 있는지 한 번 봐주실 수 있으실까요? 부탁드립니다ㅜ오류 부분calculate.test.jsimport { render, screen } from "../../../test-utils"; import userEvent from "@testing-library/user-event"; import Type from "../Type"; import OrderPage from "../OrderPage"; test("update products total when products change", async () => { render(<Type orderType="products" />); const productsTotal = screen.getByText("상품 총 가격: ", { exact: false }); expect(productsTotal).toHaveTextContent("0"); // 아메리카 여행 상품 한개 올리기 const americaInput = await screen.findByRole("spinbutton", { name: "America", }); userEvent.clear(americaInput); userEvent.type(americaInput, "1"); // 이 상품을 하나 산다는 뜻 expect(americaInput).toHaveTextContent("1000"); }); - Type.jsimport React, { useContext, useEffect, useState } from "react"; import Products from "./Products"; import axios from "axios"; import ErrorBanner from "../../components/ErrorBanner"; import Options from "./Options"; import { OrderContext } from "../../contexts/OrderContext"; const Type = ({ orderType }) => { const [items, setItems] = useState([]); const [error, setError] = useState(false); const [orderDatas, updateItemCount] = useContext(OrderContext); // OrderContext.js의 return [{ ...orderCounts, totals }, updateItemCount]; 을 구조분해 useEffect(() => { loadItems(orderType); }, [orderType]); const loadItems = async (orderType) => { try { let response = await axios.get(`http://localhost:5000/${orderType}`); setItems(response.data); } catch (error) { setError(true); } }; if (error) { return <ErrorBanner message="에러가 발생했습니다" />; } const ItemComonents = orderType === "products" ? Products : Options; const optionItems = items.map((item) => ( <ItemComonents style={{ border: "2px solid red" }} key={item.name} name={item.name} imagePath={item.imagePath} updateItemCount={(itemName, newItemCount) => updateItemCount(itemName, newItemCount, orderType) } /> )); let orderTypeKorean = orderType === "products" ? "상품" : "옵션"; return ( <div> <h2>주문종류</h2> <p>하나의 가격</p> <p> {orderTypeKorean} 총 가격: {orderDatas.totals[orderType]} </p> <div style={{ display: "flex", flexDirection: orderType === "options" && "column", // }} > {optionItems} </div> </div> ); }; export default Type; orderContext.jsimport { createContext, useState, useMemo, useEffect } from "react"; export const OrderContext = createContext(); const pricePerItem = { products: 1000, options: 500, }; function calculateSubtotal(orderType, orderCounts) { let optionCount = 0; for (const count of orderCounts[orderType].values()) { optionCount += count; } return optionCount * pricePerItem[orderType]; } export function OrderContextProvider(props) { const [orderCounts, setOrderCounts] = useState({ products: new Map(), options: new Map(), }); const [totals, setTotals] = useState({ products: 0, options: 0, total: 0, }); useEffect(() => { const productsTotal = calculateSubtotal("products", orderCounts); const optionsTotal = calculateSubtotal("options", orderCounts); const total = productsTotal + optionsTotal; setTotals({ products: productsTotal, options: optionsTotal, total, }); }, [orderCounts]); const value = useMemo(() => { function updateItemCount(itemName, newItemCount, orderType) { const newOrderCounts = { ...orderCounts }; const orderCountsMap = orderCounts[orderType]; orderCountsMap.set(itemName, parseInt(newItemCount)); setOrderCounts(newOrderCounts); } return [{ ...orderCounts, totals }, updateItemCount]; }, [orderCounts, totals]); return <OrderContext.Provider value={value} {...props} />; }
-
미해결따라하며 배우는 리액트 테스트 [2023.11 업데이트]
es6 jest 미지원 오류 문의
안녕하세요 강의 잘 듣고 있습니다. 강의를 듣다가 axios를 설치하고 import 하는 과정에서 다음과 같은 문제가 발생했습니다. 구글링을 해보니 jest가 es6를 지원하지 않아서 발생하는 문제라고 하던데 구글링해서 찾아본 방법들은 해결이 되지 않아 문의 드립니다. FAIL src/pages/OrderPage/tests/Type.test.js ● Test suite failed to run Jest encountered an unexpected token Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax. Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration. By default "node_modules" folder is ignored by transformers. Here's what you can do: • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it. • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config. • If you need a custom transformation specify a "transform" option in your config. • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option. You'll find more details and examples of these config options in the docs: https://jestjs.io/docs/configuration For information about custom transformations, see: https://jestjs.io/docs/code-transformation Details: C:\Users\multicampus\Desktop\projects\react-test-app\react-shop-test\node_modules\axios\index.js:1 ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import axios from './lib/axios.js'; ^^^^^^ SyntaxError: Cannot use import statement outside a module > 1 | import axios from 'axios'; | ^ 2 | import React, { useEffect, useState } from 'react' 3 | import { Products } from './Products'; 4 | at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1728:14) at Object.<anonymous> (src/pages/OrderPage/Type.js:1:1) at Object.<anonymous> (src/pages/OrderPage/tests/Type.test.js:2:1) at TestScheduler.scheduleTests (node_modules/@jest/core/build/TestScheduler.js:333:13) at runJest (node_modules/@jest/core/build/runJest.js:404:19)
-
미해결따라하며 배우는 리액트 테스트 [2023.11 업데이트]
axios 1.1.2 버전 issue ( SyntaxError: Cannot use import statement outside a module)
혹시나 에러가 나신다면, package.json폴더에 "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --transformIgnorePatterns \"node_modules/(?!axios)/\"", "eject": "react-scripts eject" },로 변경 후 test를 종료 후 재 실행시키면 됩니다.방법은 test에서 직접 스크립트 수정하거나 jest.config.js파일을 만들어 moduleNameMapper을 사용하시면 됩니다!참고https://stackoverflow.com/questions/73958968/cannot-use-import-statement-outside-a-module-with-axioshttps://jestjs.io/docs/configuration#modulenamemapper-objectstring-string--arraystring
-
미해결따라하며 배우는 리액트 테스트 [2023.11 업데이트]
jest와 @testing-library/jest-dom
너무 기초적인 질문인 것 같아 걱정이지만 질문드립니다~! 1. jest와 @testing-library/jest-dom는 어떤 차이가 있고 각각의 역할이 궁금합니다. 2. @testing-library와 함께 사용하는 이유와 jest만 따로 사용하지는 않는 이유가 궁금합니다.